"""Playing with blocks."""
import numpy as np
import sys
sys.path.insert(0, '../../vera')
import timedata

class Let(object):
    """Base for Inlet and Outlet."""
    @property
    def fieldName(self):
        return self._fieldName
    @fieldName.setter
    def fieldName(self, x):
        self._fieldName = x
    
    def __init__(self, block, **kwargs):
        """Initialization of Let instance.
        
        Arguments:
            block -- parent block
            fieldName (keyword; optional)
        """
        self._block = block
        # Dictionary of TimeData
        self._tds = {}
        self._fieldName = kwargs["fieldname"] if kwargs.has_key("fieldName") \
                          else "y"
    
    def __getitem__(self, key):
        """Indexing will access data elements."""
        return self._tds[key]
        
    def __setitem__(self, key, value):
        """Indexing will access data elements."""
        self._tds[key] = value

    def GetY(self, index=0):
        """Accesses fieldName attribute from TimeData given by index."""
        return self[index].__getattribute__(self._fieldName)

    def SetY(self, value, index=0):
        """Sets fieldName attribute from TimeData given by index."""
        return self[index].__setattr__(self._fieldName, value)

    
class Inlet(Let):
    # Read-only property indicating whether the number of TimeData objects
    # is at least equal to self._size
    @property
    def flagFull(self):
        return len(self._tds) >= self._size
        
    def __init__(self, block, **kwargs):
        """Initialization of Let instance.
        
        Arguments:
            block -- parent block
            size=1 (keyword; optional) -- number of TimeData that need to be
                set until parent block is pulsed
        """
        Let.__init__(self, block, **kwargs)
        self._size = kwargs["size"] if kwargs.has_key("size") else 1
        
    def __setitem__(self, key, value):
        """Pulses block if number of TimeData equals _size."""
        Let.__setitem__(self, key, value)
        if self.flagFull:
            self._block._Pulse()

class Outlet(Let):
    @property
    def inlets(self):
        """List of inlets connected to the Outlet."""
        return self._inlets
        
    def __init__(self, *args, **kwargs):
        Let.__init__(self, *args, **kwargs)
        self._inlets = []
        
    def Connect(self, dest):
        if isinstance(dest, Block):
            # Assumes that the block has an inlet called "input"
            self._inlets.append(dest["input"])
        elif isinstance(dest, Inlet):
            self._inlets.append(dest)
        else:
            raise RuntimeError("Destination of invalid class ``%s``" % \
                (dest.__class__.__name__))
        self._SendData(self._inlets[-1])
    
    def _SendData(self, inlet):
        for key, value in self._tds:
            inlet[key] = value
    
    def __setitem__(self, key, value):
        """Transmits value to all connections straight away."""
        Let.__setitem__(self, key, value)
        for inlet in self._inlets:
            inlet[key] = value

class Block(object):
    def Connect(self, dest):
        """Calls Outlet's Connect().
        
        Makes sense only if the block has only one Outlet."""
        if len(self._outlets) != 1:
            raise RuntimeError("Cannot call Block.Connect() if number of Outlets is not 1 (%d)" % len(self._outlets))
        d = self._outlets
        d[d.keys()[0]].Connect(dest)
        
    def __init__(self):
        self._inlets = {}
        self._outlets = {}
        # Contains simultaneously all keys from _inlets and _outlets
        self._alllets = {}
        self._AddLets()
        
    def _AddLets(self):
        """Inherit this to add Inlets and Outlets."""
        pass
        
    def __getitem__(self, key):
        """Makes it possible to access inlets/outlets using indexing.
        
        For example: block["input"]; block["output"]
        """
        return self._alllets[key]
        
    def __setitem__(self, key, value):
        """Sets _alllets[key][0] with value."""
        self._alllets[key][0] = value
        
    def _FindName(self, prefix, d):
        i = 0
        while True:
            name = "%s%1d" % (prefix, i)
            if not d.has_key(name):
                flag = True
                break
            i += 1
        self._lastName = name
        return name
    
    def _AddInlet(self, name=None, inletClass=None):
        """To be called from __init__()
        Arguments:
            name -- inlet name. If not specified, a name "input<n>" will be
                    made up, where <n> has one digit only.
            inletClass --- defaults to Inlet.
        """
        if name is None:
            name = self._FindName("input", self._inlets)
        assert not self._inlets.has_key(name)
        assert not self._alllets.has_key(name)
        inlet = Inlet(self) if inletClass is None else inletClass(self)
        self._inlets[name] = inlet
        self._alllets[name] = inlet

    def _AddOutlet(self, name=None):
        """So far there is one type of Outlet only.
        Arguments:
            name -- outlet name. If not specified, a name "output<n>" will be
                    made up, where <n> has one digit only.
        """
        if name is None:
            name = self._FindName("output", self._outlets)
        assert not self._outlets.has_key(name)
        assert not self._alllets.has_key(name)
        outlet = Outlet(self)
        self._outlets[name] = outlet
        self._alllets[name] = outlet

    def _Pulse(self):
        """Checks number of inputs and calls GenerateOutput or Train()
        
        TODO: training not implemented."""
        if all([x.flagFull for x in self._inlets.itervalues()]):
            # TODO: Here there could be a switch (either training or generating output)
            self._Use()


class IBlock(Block):
    """Block with one input and no output."""
    def _AddLets(self):
        self._AddInlet("input")
        self._AddOutlet("output")

class OBlock(Block):
    """Block with one output and no input."""
    def _AddLets(self):
        self._AddOutlet("output")
    

class IOBlock(Block):
    """Block with one input and one output."""
    def _AddLets(self):
        self._AddInlet("input", YInlet)
        self._AddOutlet("output")

class Printer(IBlock):
    def __init__(self):
        IBlock.__init__(self)
        
    def _Use(self):
        print self["input"].GetY()
        
class Times(OBlock):
    def _AddLets(self):
        OBlock._AddLets(self)
        self._AddInlet("a")
        self._AddInlet("b")
        
    def _Use(self): 
        # Gotta get used
        # Shortcut to self._alllets["a"]._tds[0].FlatCopy()
        r = self["a"][0].FlatCopy()
        r.y = self["a"].GetY()*self["b"].GetY()
        self["output"][0] = r




### Testing this shit
td1 = timedata.Generic()
td1.SetSize(5)
td1.y[:] = [1, 2, 3, 4, 5]

td2 = timedata.Generic()
td2.SetSize(5)
td2.y[:] = [2, 2, 2, 1, 1]

blk = Times()
vis = Printer()
#blk.Connect(vis)

blk["a"] = td1
blk["b"] = td2

pass
